package com.up.vms.infrastructure.util;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * ClassName: InfoDesensitizeUtil <br/>
 * Function: 信息脱敏类. <br/>
 *
 * @author Carbin
 */
public class InfoDesensitizeUtil {

    private static final Logger logger = LoggerFactory.getLogger(InfoDesensitizeUtil.class);

    private static final String COMMA = ",";
    private static final String COLON = ":";
    private static final String BRACES_LEFT = "{";
    private static final String BRACES_RIGHT = "}";
    private static final String SQUARE_BRACKETS_LEFT = "[";
    private static final String SQUARE_BRACKETS_RIGHT = "]";
    private static final String KEY_FORMAT_STRING = "\"%s\"";

    private static ThreadLocal<SimpleDateFormat> df = new ThreadLocal<SimpleDateFormat>() {
        @Override
        protected synchronized SimpleDateFormat initialValue() {
            return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        }
    };
    private static final String EMAIL_REG = "^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$";
    private static final String MOBILE_REG = "^(1[3,4,5,7,8]\\d)(\\d{4})(\\d{4})$";
    private static final String IDCARD_REG = "^(\\d{6})(\\d{5}|\\d{8})(\\d{3}[0-9,x,X])$";
    private static final String BANKACCT_REG = "^(\\d{6})(\\d{6}|\\d{9})(\\d{4})$";
    private static final String MD5_PASSOWRD_REG = "^([a-zA-Z0-9]{32})$";
    private static final String RSA_PASSOWRD_REG = "^([a-zA-Z0-9]{64,})$";

    private static final Pattern MOBILE_PATTERN = Pattern.compile(MOBILE_REG);
    private static final Pattern IDCARD_PATTERN = Pattern.compile(IDCARD_REG);
    private static final Pattern BANKACCT_PATTERN = Pattern.compile(BANKACCT_REG);
    private static final char STAR = '*';
    private static final String STARS = "*****";

    private static final String COVERT_ERROR_MSG = "An error ocur when covert object to String!";

    private InfoDesensitizeUtil() {

    }

    public static String fromObject(final Object object) {
        final StringBuilder sb = new StringBuilder();
        try {
            if (object == null) {
                return null;
            }
            if (isArray(object)) {
                sb.append(covertFromArray(object));
            } else if (isMap(object)) {
                sb.append(covertFromMap(object));
            } else if (isCollection(object)) {
                sb.append(covertFromCollection(object));
            } else if (isNumber(object)) {
                sb.append(covertFromNumber(object));
            } else if (isBoolean(object)) {
                sb.append(covertFromBoolean(object));
            } else if (isString(object)) {
                sb.append(covertFromString(object));
            } else if (isDate(object)) {
                sb.append(covertFromDate(object));
            } else {
                sb.append(convertFromObject(object));
            }
        } catch (Exception e) {
            logger.warn(COVERT_ERROR_MSG);
            return COVERT_ERROR_MSG;
        }
        return sb.toString();
    }

    private static Object covertFromDate(Object object) {
        return String.format(KEY_FORMAT_STRING, df.get().format(object));
    }

    private static Object covertFromBoolean(Object object) {
        return String.valueOf(object);
    }

    private static Object covertFromString(Object object) {
        return String.format(KEY_FORMAT_STRING, encryptSensiveInfo(String.valueOf(object)));
    }

    private static Object covertFromNumber(final Object object) {
        return encryptSensiveInfo(String.valueOf(object));
    }

    private static String covertFromArray(final Object object) {
        final StringBuilder sb = new StringBuilder(SQUARE_BRACKETS_LEFT);
        for (int i = 0; i < Array.getLength(object); i++) {
            sb.append(fromObject(Array.get(object, i))).append(COMMA);
        }
        if (sb.indexOf(COMMA) > 0) {
            sb.deleteCharAt(sb.lastIndexOf(COMMA));
        }
        sb.append(SQUARE_BRACKETS_RIGHT);
        return sb.toString();
    }

    private static String convertFromObject(final Object object) {
        final StringBuilder sb = new StringBuilder(BRACES_LEFT);
        final Method[] methods = object.getClass().getMethods();
        for (Method method : methods) {
            String methodName = method.getName();
            if (methodName.startsWith("get") && (!methodName.endsWith("Class")
                    && !methodName.endsWith("getOrderFor")
                    && !methodName.endsWith("getAppClassLoader")
                    && !methodName.endsWith("getResourceAsStream")
                    && !methodName.endsWith("getContent")
            )
            ) {
                try {
                    sb.append(String.format(KEY_FORMAT_STRING, unCapitalise(methodName.substring(3)))).append(COLON)
                            .append(fromObject(method.invoke(object, new Object[]{}))).append(COMMA);

                } catch (Exception e) {
                    logger.warn("An error ocur when convertFromObject!" + e.toString() + ",methodName:" + methodName);
                }
            }
        }
        if (sb.indexOf(COMMA) > 0) {
            sb.deleteCharAt(sb.lastIndexOf(COMMA));
        }
        sb.append(BRACES_RIGHT);
        return sb.toString();
    }

    @SuppressWarnings("rawtypes")
    private static String covertFromMap(final Object object) {
        final StringBuilder sb = new StringBuilder(BRACES_LEFT);
        final Map map = (Map) object;
        final Iterator it = map.keySet().iterator();
        while (it.hasNext()) {
            final Object key = it.next();
            sb.append(String.format(KEY_FORMAT_STRING, String.valueOf(key))).append(COLON)
                    .append(fromObject(map.get(key))).append(COMMA);
        }
        if (sb.indexOf(COMMA) > 0) {
            sb.deleteCharAt(sb.lastIndexOf(COMMA));
        }
        sb.append(BRACES_RIGHT);
        return sb.toString();
    }

    @SuppressWarnings("rawtypes")
    private static String covertFromCollection(final Object object) {
        final StringBuilder sb = new StringBuilder(SQUARE_BRACKETS_LEFT);
        final Collection c = (Collection) object;
        final Iterator it = c.iterator();
        while (it.hasNext()) {
            sb.append(fromObject(it.next())).append(COMMA);
        }
        if (sb.indexOf(COMMA) > 0) {
            sb.deleteCharAt(sb.lastIndexOf(COMMA));
        }
        sb.append(SQUARE_BRACKETS_RIGHT);
        return sb.toString();
    }

    /**
     * @param object
     * @return 判断对象是否为数字
     */
    @SuppressWarnings("rawtypes")
    private static boolean isNumber(final Object object) {
        if (object == null) {
            return false;
        }
        final Class clazz = object.getClass();
        if (clazz == Byte.TYPE || clazz == Short.TYPE || clazz == Integer.TYPE) {
            return true;
        } else if (clazz == Long.TYPE || clazz == Float.TYPE || clazz == Double.TYPE) {
            return true;
        } else {
            // Integer、Long、Byte、Double、Float、Short均是Number的子类
            return object instanceof Number;
        }
    }

    /**
     * @param object
     * @return 对象是否为boolean类型
     */
    private static boolean isBoolean(final Object object) {
        if (object == null) {
            return false;
        }
        return object instanceof Boolean || object.getClass() == Boolean.TYPE;
    }

    /**
     * @param object
     * @return 对象是否为String类型。String或者Char类型均认为是String类型
     */
    @SuppressWarnings("rawtypes")
    private static boolean isString(final Object object) {
        if (object == null) {
            return false;
        }
        final Class clazz = object.getClass();
        return String.class.isAssignableFrom(clazz) || Character.TYPE.isAssignableFrom(clazz)
                || Character.class.isAssignableFrom(clazz);
    }

    /**
     * @param object
     * @return 判断对象是否为数组
     */
    private static boolean isArray(final Object object) {
        if (object == null) {
            return false;
        }
        return object.getClass().isArray();
    }

    /**
     * @param object
     * @return 判断对象是否为集合
     */
    private static boolean isCollection(final Object object) {
        if (object == null) {
            return false;
        }
        return object instanceof Collection;
    }

    /**
     * @param object
     * @return 判断对象是否为时间类型
     */
    private static boolean isDate(Object object) {
        if (object == null) {
            return false;
        }
        return object instanceof Date;
    }

    /**
     * @param object
     * @return 判断对象是否为Map类型
     */
    private static boolean isMap(final Object object) {
        if (object == null) {
            return false;
        }
        return object instanceof Map;
    }

    /**
     * @param str
     * @return 首字母小写
     */
    private static String unCapitalise(String str) {
        if (str != null && str.length() >= 0) {
            char capital = str.charAt(0);
            String temp = String.valueOf(capital);
            if (capital >= 'A' && capital <= 'Z') {
                return str.replaceFirst(temp, temp.toLowerCase());
            }
        }
        return str;
    }

    /**
     * @param str
     * @return 对邮箱、手机号、身份证、银行卡等信息进行机密显示
     */
    public static String encryptSensiveInfo(String str) {
        try {
            if (StringUtils.isEmpty(str)) {
                return str;
            }
            if (str.matches(EMAIL_REG)) {
                return encryptEmail(str);
            } else if (str.matches(MOBILE_REG)) {
                return hideGroup(MOBILE_PATTERN, str, STAR, 2);
            } else if (str.matches(IDCARD_REG)) {
                return hideGroup(IDCARD_PATTERN, str, STAR, 2);
            } else if (str.matches(BANKACCT_REG)) {
                return hideGroup(BANKACCT_PATTERN, str, STAR, 2);
            } else if (str.matches(MD5_PASSOWRD_REG)) {
                return STARS;
            } else if (str.matches(RSA_PASSOWRD_REG)) {
                return STARS;
            }
            return str;
        } catch (Exception e) {
            logger.warn(COVERT_ERROR_MSG);
            return COVERT_ERROR_MSG;
        }
    }

    private static String encryptEmail(String email) {
        StringBuilder sb = new StringBuilder(email);
        // 邮箱账号名只显示前两位
        int mailNamePosition = sb.indexOf("@");
        if (mailNamePosition > 2) {
            sb.replace(2, mailNamePosition, StringUtils.repeat("*", mailNamePosition - 2));
        }
        // 邮箱域名隐藏
        int domainNamePosition = sb.lastIndexOf(".");
        sb.replace(mailNamePosition + 1, domainNamePosition, StringUtils.repeat("*", domainNamePosition - mailNamePosition - 1));
        return sb.toString();
    }

    /**
     * @param pattern
     * @param str
     * @return 利用正则表达式，隐藏中间字符。
     */
    private static String hideGroup(Pattern pattern, String str, char replaceChar, int... position) {
        try {
            StringBuilder sb = new StringBuilder();
            Matcher matcher = pattern.matcher(str);
            if (matcher.matches()) {
                replaceWithSpecialChar(matcher, sb, replaceChar, position);
            } else {
                sb.append(str);
            }
            return sb.toString();
        } catch (RuntimeException e) {
            logger.warn(e.toString());
            return str;
        }
    }

    private static void replaceWithSpecialChar(Matcher matcher, StringBuilder sb, char replaceChar, int... position) {
        for (int i = 1; i <= matcher.groupCount(); i++) {
            if (contains(position, i)) {
                char[] temp = new char[matcher.group(i).length()];
                Arrays.fill(temp, replaceChar);
                sb.append(temp);
            } else {
                sb.append(matcher.group(i));
            }
        }
    }

    private static boolean contains(int[] ary, int element) {
        if (ary == null || ary.length == 0) {
            return false;
        }
        for (int i = 0; i < ary.length; i++) {
            if (element == ary[i]) {
                return true;
            }
        }
        return false;
    }
}
